package com.yb.core.aop;

import com.yb.core.annotation.RateLimit;
import com.yb.core.constant.HttpConstant;
import com.yb.core.exception.YbBusinessException;
import com.yb.core.util.RequestUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

/**
 * @author zhaojie
 */
@Aspect
@Configuration
@EnableAspectJAutoProxy
@Order(Ordered.LOWEST_PRECEDENCE)
public class LimitAspect implements ApplicationContextAware, InitializingBean {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private ApplicationContext applicationContext;
    @Autowired
    private RedisTemplate<String, Serializable> limitRedisTemplate;
    @Autowired
    private DefaultRedisScript<Long> redisluaScript;

    LocalVariableTableParameterNameDiscoverer localVariableTable = new LocalVariableTableParameterNameDiscoverer();

    ExpressionParser parser = new SpelExpressionParser();

    TemplateParserContext templateParserContext = new TemplateParserContext();


    BeanFactoryResolver beanFactoryResolver = null;

    @Around("execution(public * com.yb.*..controller.*.*(..)) && @annotation(com.yb.core.annotation.RateLimit)")
    public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {

        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        RateLimit rateLimit = method.getAnnotation(RateLimit.class);

        if (rateLimit != null) {
            HttpServletRequest request =
                ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

            Object[] args = joinPoint.getArgs();

            String rateLimitKey = this.getKey(method, args, rateLimit.key());

            //如果没有通过ip限流并且没有指定key,则直接放行
            if(!rateLimit.ip()&& !StringUtils.hasText(rateLimitKey)){
                return joinPoint.proceed();
            }
            StringBuffer stringBuffer = new StringBuffer();
            if(rateLimit.ip()){
                String ipAddress = RequestUtil.getIpAddr(request);
                stringBuffer.append(ipAddress).append("-");
            }
            stringBuffer.append(targetClass.getName()).append("-")
                .append(method.getName());

            if(StringUtils.hasText(rateLimitKey)){
                stringBuffer.append("-").append(rateLimitKey);
            }
            List<String> keys = Collections.singletonList(stringBuffer.toString());
            Number number = limitRedisTemplate.execute(redisluaScript, keys, rateLimit.count(), rateLimit.time());

            if (number != null && number.intValue() != 0 && number.intValue() <= rateLimit.count()) {
                logger.info("限流时间段内访问第：{} 次", number.toString());
                return joinPoint.proceed();
            }
        } else {
            return joinPoint.proceed();
        }
        throw new YbBusinessException(HttpConstant.Response.REQUEST_FREQUENTLY);
    }


    private String getKey(Method method,Object[] args,String key){
        //获取被拦截方法参数名列表(使用Spring支持类库)
        if(!StringUtils.hasText(key))return "";
        String[] paraNameArr = localVariableTable.getParameterNames(method);
        //使用SPEL进行key的解析

        //SPEL上下文
        StandardEvaluationContext evaluationContext = new StandardEvaluationContext();


        evaluationContext.setBeanResolver(this.beanFactoryResolver);

        //把方法参数放入SPEL上下文中
        for(int i=0;i<paraNameArr.length;i++) {
            evaluationContext.setVariable(paraNameArr[i], args[i]);
        }
        String resultKey = null;
        // 使用变量方式传入业务动态数据
//        if(key.matches("^#.*.$")) {
//            resultKey = parser.parseExpression(key).getValue(context, String.class);
//        }
        if(isTemplate(key)){
            resultKey = parser.parseExpression(key,templateParserContext).getValue(evaluationContext,String.class);
        }else{
            resultKey = parser.parseExpression(key).getValue(evaluationContext,String.class);
        }


        return resultKey;
    }

    private static final boolean isTemplate(String key){
        if(key.startsWith("#{"))return true;
        return false;
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.beanFactoryResolver=new BeanFactoryResolver(this.applicationContext);
    }
}